package logger

import (
	"fmt"
	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
	"github.com/sirupsen/logrus"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"time"
)

type Logger interface {
	Panic(...interface{})
	Panicf(string, ...interface{})
	Fatal(...interface{})
	Fatalf(string, ...interface{})
	Error(...interface{})
	Errorf(string, ...interface{})
	Warn(...interface{})
	Warnf(string, ...interface{})
	Info(...interface{})
	Infof(string, ...interface{})
	Debug(...interface{})
	Debugf(string, ...interface{})
	WithFields(Fields) Logger
	WithField(string, interface{}) Logger
	SetLevel(Level)
	ErrorV(err error)
}

const (
	PanicLevel Level = iota
	FatalLevel
	ErrorLevel
	WarnLevel
	InfoLevel
	DebugLevel
)

const (
	FormatJson = "json"
	FormatText = "text"
	OutFile    = "file"
	OutStdout  = "stdout"

	maxDepth = 32
	depth    = 1
)

type Level int

type LogEntry struct {
	entry       *logrus.Entry
	isShortName bool
}

type Fields map[string]interface{}

type Options struct {
	level         Level         //日志等级
	path          string        //文件存放路径
	rotationCount uint          //保留文件数量
	rotationTime  time.Duration //文件切割间隔
	formatter     string        //日志格式
	out           string        //日志输出方式
	isShortName   bool          //是否使用短文件名
}

type ModOption func(opt *Options)

func SetPath(path string) ModOption {
	return func(opt *Options) {
		opt.path = path
	}
}

func SetLevel(level Level) ModOption {
	return func(opt *Options) {
		opt.level = level
	}
}

func SetRotationCount(count uint) ModOption {
	return func(opt *Options) {
		opt.rotationCount = count
	}
}

func SetRotationTime(hours time.Duration) ModOption {
	return func(opt *Options) {
		opt.rotationTime = hours * time.Hour
	}
}

func SetFormatter(format string) ModOption {
	return func(opt *Options) {
		opt.formatter = format
	}
}

func SetOut(out string) ModOption {
	return func(opt *Options) {
		opt.out = out
	}
}

func SetFileNameRule(isShort bool) ModOption {
	return func(opt *Options) {
		opt.isShortName = isShort
	}
}

func New(modOption ...ModOption) *LogEntry {
	log := logrus.New()

	options := &Options{
		level:         DebugLevel,
		path:          "logs",
		rotationCount: 30,
		rotationTime:  24 * time.Hour,
		formatter:     FormatJson,
		out:           OutFile,
		isShortName:   false,
	}

	for _, fn := range modOption {
		fn(options)
	}

	var formatter logrus.Formatter
	if options.formatter == FormatJson {
		formatter = &logrus.JSONFormatter{

		}
	} else if options.formatter == FormatText {
		formatter = &logrus.TextFormatter{
			DisableColors: os.Getenv("NOCOLOR") != "",
			FullTimestamp: true,
		}
	}

	log.Formatter = formatter

	if options.out == OutFile {
		//需要写入文件时，才检查路径
		file, err := os.Stat(options.path)
		if err != nil && os.IsNotExist(err) {
			if err := os.MkdirAll(options.path, 0755); err != nil {
				return nil
			}
		} else if file.Mode() != 0755 {
			if err := os.Chmod(options.path, 0755); err != nil {
				return nil
			}
		}

		logsWriter, _ := rotatelogs.New(
			filepath.Join(options.path, "%Y-%m-%d.log"),         //文件名规则
			rotatelogs.WithRotationCount(options.rotationCount), //保留x天的数据
			rotatelogs.WithRotationTime(options.rotationTime),   //每天分割一次
		)

		log.Out = logsWriter
	} else if options.out == OutStdout {
		log.Out = os.Stdout
	}

	// 直接输出不需要hooks
	//log.Hooks.Add(lfshook.NewHook(
	//   lfshook.WriterMap{
	//       logrus.DebugLevel: logsWriter,
	//       logrus.WarnLevel:  logsWriter,
	//       logrus.InfoLevel:  logsWriter,
	//       logrus.ErrorLevel: logsWriter,
	//       logrus.FatalLevel: logsWriter,
	//       logrus.PanicLevel: logsWriter,
	//   },
	//   formatter,
	//))

	entry := &LogEntry{
		entry:       logrus.NewEntry(log),
		isShortName: options.isShortName,
	}
	entry.SetLevel(options.level)
	return entry
}

func (e *LogEntry) Infof(msg string, args ...interface{}) {
	e.entry.WithField("call", caller(depth, e.isShortName)).Infof(msg, args...)
}

func (e *LogEntry) Info(args ...interface{}) {
	e.entry.WithField("call", caller(depth, e.isShortName)).Info(args...)
}

func (e *LogEntry) Errorf(msg string, args ...interface{}) {
	e.entry.WithField("call", caller(depth, e.isShortName)).Errorf(msg, args...)
}

func (e *LogEntry) Error(args ...interface{}) {
	e.entry.WithField("call", caller(depth, e.isShortName)).Error(args...)
}

func (e *LogEntry) ErrorV(error error) {
	str := strings.Replace(fmt.Sprintf("%+v", error), "\t", "", -1)
	stack := strings.Split(str, "\n")

	var (
		skip = 3
		st   []string
		min  int
	)

	if len(stack) > skip {
		min = skip
	} else {
		min = 0
	}

	if len(stack) > maxDepth {
		st = stack[min:maxDepth]
	} else {
		st = stack[min:]
	}
	e.entry.WithField("stack", st).WithField("call", caller(depth, e.isShortName)).Error(error.Error())
}

func (e *LogEntry) Warnf(msg string, args ...interface{}) {
	e.entry.WithField("call", caller(depth, e.isShortName)).Warnf(msg, args...)
}

func (e *LogEntry) Warn(args ...interface{}) {
	e.entry.WithField("call", caller(depth, e.isShortName)).Warn(args...)
}

func (e *LogEntry) Debugf(msg string, args ...interface{}) {
	e.entry.WithField("call", caller(depth, e.isShortName)).Debugf(msg, args...)
}

func (e *LogEntry) Debug(args ...interface{}) {
	e.entry.WithField("call", caller(depth, e.isShortName)).Debug(args...)
}

func (e *LogEntry) Fatalf(msg string, args ...interface{}) {
	e.entry.WithField("call", caller(depth, e.isShortName)).Fatalf(msg, args...)
}

func (e *LogEntry) Fatal(args ...interface{}) {
	e.entry.WithField("call", caller(depth, e.isShortName)).Fatal(args...)
}

func (e *LogEntry) Panicf(msg string, args ...interface{}) {
	e.entry.WithField("call", caller(depth, e.isShortName)).Panicf(msg, args...)
}

func (e *LogEntry) Panic(args ...interface{}) {
	e.entry.WithField("call", caller(depth, e.isShortName)).Panic(args...)
}

func (e *LogEntry) SetLevel(level Level) {
	switch level {
	case PanicLevel:
		e.entry.Logger.Level = logrus.PanicLevel
	case FatalLevel:
		e.entry.Logger.Level = logrus.FatalLevel
	case ErrorLevel:
		e.entry.Logger.Level = logrus.ErrorLevel
	case WarnLevel:
		e.entry.Logger.Level = logrus.WarnLevel
	case InfoLevel:
		e.entry.Logger.Level = logrus.InfoLevel
	case DebugLevel:
		e.entry.Logger.Level = logrus.DebugLevel
	}
}

func (e *LogEntry) WithFields(fields Fields) Logger {
	return &LogEntry{
		entry:       e.entry.WithFields(map[string]interface{}(fields)),
		isShortName: e.isShortName,
	}
}

func (e *LogEntry) WithField(key string, val interface{}) Logger {
	return &LogEntry{
		entry:       e.entry.WithFields(map[string]interface{}{key: val}),
		isShortName: e.isShortName,
	}
}

func caller(depth int, shortFile bool) string {
	_, file, line, ok := runtime.Caller(depth + 1)

	if !ok {
		return ""
	}

	var filename = file
	if shortFile {
		filename = filepath.Base(filename)
	}

	return fmt.Sprintf("%v:%v", filename, line)
}
